using System;
using System.Collections.Generic;
using System.Reflection;

namespace WCSFContrib.Extensions.EventBroker
{
	public class EventBrokerSanityCheck
	{
		public Dictionary<Type, List<MemberReference<EventInfo>>> GetPublishingHandlers(IEnumerable<Type> typesToInspect)
		{
			Dictionary<Type, List<MemberReference<EventInfo>>> publishingHandlers = new Dictionary<Type, List<MemberReference<EventInfo>>>();

			foreach (Type typeToInspect in typesToInspect)
			{
				List<MemberReference<EventInfo>> eventList = GetPublishingEvents(typeToInspect);
				if (eventList.Count > 0)
					publishingHandlers.Add(typeToInspect, eventList);
			}

			return publishingHandlers;
		}

		public Dictionary<Type, List<MemberReference<MethodInfo>>> GetSubscribingHandlers(IEnumerable<Type> typesToInspect)
		{
			Dictionary<Type, List<MemberReference<MethodInfo>>> subscribingHandlers = new Dictionary<Type, List<MemberReference<MethodInfo>>>();

			// Find all pubs and subs on each type
			foreach (Type typeToInspect in typesToInspect)
			{
				List<MemberReference<MethodInfo>> methodList = GetSubscribingMethods(typeToInspect);
				if (methodList.Count > 0)
					subscribingHandlers.Add(typeToInspect, methodList);
			}
			return subscribingHandlers;
		}

		public void PerformSanityCheck(
			Dictionary<Type, List<MemberReference<EventInfo>>> publishingHandlers,
			Dictionary<Type, List<MemberReference<MethodInfo>>> subscribingHandlers)
		{
			// Organize all publising events as a list of events with the topic as key
			Dictionary<string, List<MemberReference<EventInfo>>> publisingTopics = SortMembersByTopic(publishingHandlers);

			// Loop all subscriptions and find lists of publishers by topic
			foreach (KeyValuePair<Type, List<MemberReference<MethodInfo>>> subscriptionsOnType in subscribingHandlers)
			{
				List<MemberReference<MethodInfo>> subscriptions = subscriptionsOnType.Value;
				foreach (MemberReference<MethodInfo> subscription in subscriptions)
				{
					List<MemberReference<EventInfo>> publications;
					if (publisingTopics.TryGetValue(subscription.Topic, out publications) && publications != null)
						// Loop all publications on this topic
						foreach (MemberReference<EventInfo> publication in publications)
						{
							// This validates all publishers against all subscribers on the same topic!
							if (!EventUtils.ValidatePublicaterSubsciberPair(publication.Info, subscription.Info))
							{
								throw new EventHookupException(new MemberReference<EventInfo>(publication.Topic, publication.Info), new MemberReference<MethodInfo>(subscription.Topic, subscription.Info), null);
							}
						}
				}
			}
		}

		/// <summary>
		/// Get all events on the type decorated with EventPubliserAttribute. Search interfaces and base classes.
		/// </summary>
		/// <param name="typeToInspect">the type to reflect</param>
		/// <returns>all the topics and events found.</returns>
		public List<MemberReference<EventInfo>> GetPublishingEvents(Type typeToInspect)
		{
			List<MemberReference<EventInfo>> results = new List<MemberReference<EventInfo>>();

			Type[] interfaces = typeToInspect.GetInterfaces();

			foreach (EventInfo publishingEventInfo in typeToInspect.GetEvents())
			{
				List<EventPublicationAttribute> attributes = new List<EventPublicationAttribute>();

				attributes.AddRange(GetCustomAttributesFromMember<EventPublicationAttribute>(publishingEventInfo));

				foreach (Type @interface in interfaces)
				{
					EventInfo interfaceInfo = @interface.GetEvent(publishingEventInfo.Name);
					if (interfaceInfo != null)
					{
						attributes.AddRange(GetCustomAttributesFromMember<EventPublicationAttribute>(interfaceInfo));
					}
				}

				foreach (EventPublicationAttribute attribute in attributes)
				{
					results.Add(new MemberReference<EventInfo>(attribute.Topic, publishingEventInfo));
				}
				//foreach (EventPublicationAttribute attribute in attributes)
				//{
				//    foreach (MethodInfo subscriber in subscribingHandlers[attribute.Topic])
				//    {
				//        if (!Utilities.ValidatePublicaterSubsciberPair(publishingEventInfo, subscriber))
				//            throw new ApplicationException("This match is not valid!");
				//    }
				//    publisingTypes.Add(type, new MemberReference<EventInfo>(attribute.Topic, publishingEventInfo));

				//    List<EventInfo> publishers;
				//    if (!publishingHandlers.TryGetValue(attribute.Topic, out publishers))
				//    {
				//        publishers = new List<EventInfo>();
				//        publishingHandlers.Add(attribute.Topic, publishers);
				//    }
				//    publishers.Add(publishingEventInfo);
				//}
			}

			return results;
		}

		/// <summary>
		/// Get all methods on the type decorated with EventSubscriberAttribute. Search interfaces and base classes.
		/// </summary>
		/// <param name="typeToInspect">the type to reflect</param>
		/// <returns>all the topics and events found.</returns>
		public List<MemberReference<MethodInfo>> GetSubscribingMethods(Type typeToInspect)
		{
			List<MemberReference<MethodInfo>> results = new List<MemberReference<MethodInfo>>();

			Type[] interfaces = typeToInspect.GetInterfaces();

			foreach (MethodInfo subscribingMethodInfo in typeToInspect.GetMethods())
			{
				List<EventSubscriptionAttribute> attributes = new List<EventSubscriptionAttribute>();

				attributes.AddRange(GetCustomAttributesFromMember<EventSubscriptionAttribute>(subscribingMethodInfo));

				foreach (Type @interface in interfaces)
				{
					ParameterInfo[] parameters = subscribingMethodInfo.GetParameters();
					List<Type> types = new List<Type>();
					foreach (ParameterInfo parameterInfo in parameters)
					{
						types.Add(parameterInfo.ParameterType);
					}
					MethodInfo interfaceInfo = @interface.GetMethod(subscribingMethodInfo.Name, types.ToArray());
					if (interfaceInfo != null)
					{
						attributes.AddRange(GetCustomAttributesFromMember<EventSubscriptionAttribute>(interfaceInfo));
					}
				}

				foreach (EventSubscriptionAttribute attribute in attributes)
				{
					results.Add(new MemberReference<MethodInfo>(attribute.Topic, subscribingMethodInfo));
				}
			}
			return results;
		}

		/// <summary>
		/// Extract the custom attribues of the type defined.
		/// </summary>
		/// <typeparam name="SAttributeType">Type of attribute to find</typeparam>
		/// <param name="info">the attribute provider to inspect</param>
		/// <returns>The custom attributes found</returns>
		public IEnumerable<SAttributeType> GetCustomAttributesFromMember<SAttributeType>(ICustomAttributeProvider info)
			 where SAttributeType : class
		{
			SAttributeType[] customAttributes = (SAttributeType[])info.GetCustomAttributes(typeof(SAttributeType), true);
			if (customAttributes != null && customAttributes.Length > 0)
				return customAttributes;
			else
				return new SAttributeType[0];
		}

		private Dictionary<string, List<MemberReference<S>>> SortMembersByTopic<S>(
			Dictionary<Type, List<MemberReference<S>>> listHandlers)
			where S : MemberInfo
		{
			Dictionary<string, List<MemberReference<S>>> handlingTopics = new Dictionary<string, List<MemberReference<S>>>();
			foreach (KeyValuePair<Type, List<MemberReference<S>>> memberTopicsOnType in listHandlers)
			{
				List<MemberReference<S>> currentMembers = memberTopicsOnType.Value;
				foreach (MemberReference<S> member in currentMembers)
				{
					List<MemberReference<S>> matches;
					if (!handlingTopics.TryGetValue(member.Topic, out matches))
					{
						matches = new List<MemberReference<S>>();
						handlingTopics.Add(member.Topic, matches);
					}
					matches.Add(member);
				}
			}
			return handlingTopics;
		}
	}
}